﻿#region 참조 네임스페이스

using Skying.ComponentModel;
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

#endregion

namespace Skying.Net.Sockets
{
	/// <summary>
	/// <see cref="System.Net.Sockets.Socket"/> 클래스에 대한 랩핑 클래스를 정의한다.
	/// </summary>
	/// <remarks>
	/// <para><see cref="TcpListener"/>의 내부 클라이언트 소켓과 <see cref="TcpClient"/>의 소켓의 데이터 송수신 메서드를 구현한다.</para>
	/// </remarks>
	public class BaseSocket
	{
		#region 상수

		/// <summary>
		/// 기본 버퍼 크기 상수를 나타낸다.
		/// </summary>
		protected const int _DEFAULT_BUFFER_SIZE = 8192;

		#endregion

		#region 변수

		/// <summary>
		/// 소켓의 고유 코드 정보를 나타낸다.
		/// </summary>
		protected Guid _id;

		/// <summary>
		/// 통신을 위한 소켓을 나타낸다.
		/// </summary>
		protected Socket _socket;

		/// <summary>
		/// 현재 수신 중인 데이터의 패킷 헤더를 나타낸다.
		/// </summary>
		private Header _currentHeader;

		/// <summary>
		/// 데이터 수신을 위한 버퍼 스트림을 나타낸다.
		/// </summary>
		private MemoryStream _bufferStream;

		/// <summary>
		/// 전체 데이터 중 수신된 데이터의 위치를 나타낸다.
		/// </summary>
		private long _receivedOffset;

		/// <summary>
		/// 데이터 중 한 조각의 버퍼 사이즈를 나타낸다.
		/// </summary>
		protected int _pieceBufferSize;

		/// <summary>
		/// 사용자 정의 데이터를 나타낸다.
		/// </summary>
		private string _userToken;

		#endregion

		#region 프로퍼티

		/// <summary>
		/// 내부 <see cref="System.Net.Sockets.Socket"/> 개체를 가져온다.
		/// </summary>
		public Socket InternalSocket
		{
			get
			{
				return this._socket;
			}
		}

		/// <summary>
		/// 소켓의 고유 코드 정보를 가져온다.
		/// </summary>
		public Guid Id
		{
			get
			{
				return this._id;
			}
		}

		/// <summary>
		/// IP 주소를 가져온다.
		/// </summary>
		public IPAddress Address
		{
			get;
			protected set;
		}

		/// <summary>
		/// 소켓의 포트 번호를 가져온다.
		/// </summary>
		public int Port
		{
			get;
			protected set;
		}

		/// <summary>
		/// <see cref="Skying.Net.BaseSocket"/>이 메모리에 배열을 정렬하는 방식을 가져온다.
		/// </summary>
		/// <remarks>
		/// <para>이 Endian 열거자는 클라이언트와 리스너 간의 <see cref="Skying.Net.Sockets.Header"/> 정보를 교환할 때 사용된다.</para>
		/// <para>패킷 중 Body 영역에는 관여하지 않으므로 유의해야 한다.</para>
		/// </remarks>
		public Endians Endian
		{
			get;
			protected set;
		}

		/// <summary>
		/// 소켓이 클라이언트로 사용될 경우 클라이언트의 유형을 가져온다.
		/// </summary>
		public ClientType ClientType
		{
			get;
			protected set;
		}

		/// <summary>
		/// 내부 헤더의 사용 여부를 가져온다.
		/// </summary>
		public bool UseInternalHeader
		{
			get;
			protected set;
		}

		/// <summary>
		/// 사용자 정의 데이터를 가져온다.
		/// </summary>
		public string UserToken
		{
			get
			{
				return this._userToken;
			}
		}

		/// <summary>
		/// 리스너와의 연결이 닫혔을 때 실행된다.
		/// </summary>
		public Action<AsyncResultEventArgs<BaseSocket>> Disconnected
		{
			get;
			set;
		}

		/// <summary>
		/// 데이터 송수신에 오류가 감지됐을 때 실행된다.
		/// </summary>
		public Action<AsyncResultEventArgs<Exception>> Error
		{
			get;
			set;
		}

		/// <summary>
		/// 데이터 패킷을 송신했을 때 실행된다.
		/// </summary>
		public Action<AsyncResultEventArgs<TransferEventArgs>> Sent
		{
			get;
			set;
		}

		/// <summary>
		/// 전체 데이터 패킷을 수신했을 때 실행된다.
		/// </summary>
		public Action<AsyncResultEventArgs<TransferEventArgs>> Received
		{
			get;
			set;
		}

		/// <summary>
		/// 데이터 패킷의 부분을 수신했을 때 실행된다.
		/// </summary>
		public Action<AsyncResultEventArgs<long>> PartReceived
		{
			get;
			set;
		}

		#endregion

		#region 생성자

		private BaseSocket(bool useInternalHeader)
		{
			try
			{
				this.UseInternalHeader = useInternalHeader;
			}
			catch (Exception error)
			{
				throw error;
			}
		}

		public BaseSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType, bool useInternalHeader)
			: this(useInternalHeader)
		{
			try
			{
				this._id = Guid.NewGuid();

				this.Endian = BitConverter.IsLittleEndian ? Endians.Little : Endians.Big;

				this._socket = new Socket(addressFamily, socketType, protocolType);
			}
			catch (Exception error)
			{
				throw error;
			}
		}

		public BaseSocket(Guid id, Socket socket, Endians endian, ClientType clientType, bool useInternalHeader, string userToken, int bufferSize = 0)
			: this(useInternalHeader)
		{
			try
			{
				this._id = id;

				this._socket = socket;

				this.Address = (this._socket.RemoteEndPoint as IPEndPoint).Address;
				this.Port = (this._socket.RemoteEndPoint as IPEndPoint).Port;

				this.Endian = endian;

				this.ClientType = clientType;

				this._userToken = userToken;

				if (bufferSize == 0)
				{
					this._pieceBufferSize = _DEFAULT_BUFFER_SIZE;
				}

				this.TurnKeepAlive(true, 60 * 1000, 1000);
			}
			catch (Exception error)
			{
				throw error;
			}
		}

		#endregion

		#region 예외 처리

		/// <summary>
		/// 발생한 예외를 동기화 콜백 처리한다.
		/// </summary>
		/// <param name="error">발생한 예외를 설정한다.</param>
		protected void ThrowError(Exception error)
		{
			Synchronization.InvokeAndCallback<Exception>(() => { return error; }, Error, null);
		}

		#endregion

		#region 소켓 컨트롤

		/// <summary>
		/// 원격 끝점과의 연결을 비동기적으로 닫는다.
		/// </summary>
		/// <param name="reuseSocket">연결을 닫은 후에 소켓을 다시 사용할 수 있으면 true, 그렇지 않으면 false를 설정한다.</param>
		public void DisconnectAsynchrous(bool reuseSocket = false)
		{
			try
			{
				this._socket.BeginDisconnect(reuseSocket, Synchronization.SynchronizeAsyncCallback((asyncResult) =>
				{
					Synchronization.InvokeAndCallback(() =>
					{
						try
						{
							this._socket.EndDisconnect(asyncResult);
						}
						catch (ObjectDisposedException)
						{
							return this;
						}
						catch (Exception error)
						{
							this.ThrowError(error);
						}

						this.Disconnected = null;
						this.Received = null;
						this.Sent = null;
						this.Error = null;

						return this;
					}, Disconnected, null);
				}), null);
			}
			catch (Exception error)
			{
				throw error;
			}
		}

		/// <summary>
		/// 비동기적으로 데이터 수신을 시작한다.
		/// </summary>
		internal void ReceiveAsynchrous()
		{
			try
			{
				var packet = new StateObject(this._socket, this._pieceBufferSize);

				this._socket.BeginReceive(packet.Buffer, 0, packet.Buffer.Length, SocketFlags.None,
					Synchronization.SynchronizeAsyncCallback((asyncResult) =>
					{
						int receivedSize = -1;

						try
						{
							SocketError socketError = new SocketError();

							receivedSize = this._socket.EndReceive(asyncResult, out socketError);

							StateObject stateObject = asyncResult.AsyncState as StateObject;

							// 수신된 데이터 크기가 0보다 큰 경우에만 수신 처리한다.
							if (receivedSize > 0)
							{
								bool received = false;

								TransferEventArgs callbackResult = null;

								long receivedLength = 0;

								if (this.UseInternalHeader)
								{
									this.AfterprocessForReceivingData(stateObject, ref received, out receivedLength, ref callbackResult);
								}
								else
								{
									this._bufferStream = new MemoryStream(receivedSize);

									this._bufferStream.Write(stateObject.Buffer, 0, stateObject.Buffer.Length);

									callbackResult = new TransferEventArgs(Header.GenerateHeader(), this._bufferStream);

									received = true;
								}

								if (received)
								{
									Synchronization.InvokeAndCallback(() =>
									{
										return callbackResult;
									}, Received, null);

									Synchronization.InvokeAndCallback(() =>
									{
										return receivedLength;
									}, PartReceived, null);
								}
							}
							// 수신된 데이터 크기가 0이거나 그보다 작은 경우 소켓을 닫는다.
							else
							{
								this.DisconnectAsynchrous();
							}
						}
						finally
						{
							if (receivedSize > 0)
							{
								this.ReceiveAsynchrous();
							}
						}
					})
					, packet);
			}
			catch (SocketException)
			{
				this.DisconnectAsynchrous();
			}
			catch (Exception error)
			{
				this.ThrowError(error);
			}
		}

		/// <summary>
		/// 지정된 네트워크 장치에서 비동기적으로 데이터 수신을 시작한다.
		/// </summary>
		/// <param name="ipEndPoint">데이터를 송신하는 대상의 <see cref="System.Net.IpEndPoint"/>를 설정한다.</param>
		internal void ReceiveAsynchrousFrom(IPEndPoint ipEndPoint)
		{
			try
			{
				var packet = new StateObject(this._socket, this._pieceBufferSize);

				EndPoint endPoint = ipEndPoint as EndPoint;

				this._socket.BeginReceiveFrom(packet.Buffer, 0, packet.Buffer.Length, SocketFlags.None, ref endPoint,
					Synchronization.SynchronizeAsyncCallback((asyncResult) =>
					{
						int receivedSize = -1;

						try
						{
							receivedSize = this._socket.EndReceiveFrom(asyncResult, ref endPoint);

							StateObject stateObject = asyncResult.AsyncState as StateObject;

							// 수신된 데이터 크기가 0보다 큰 경우에만 수신 처리한다.
							if (receivedSize > 0)
							{
								bool received = false;

								TransferEventArgs callbackResult = null;

								long receivedLength = 0;

								if (this.UseInternalHeader)
								{
									this.AfterprocessForReceivingData(stateObject, ref received, out receivedLength, ref callbackResult);
								}
								else
								{
									this._bufferStream = new MemoryStream(receivedSize);

									this._bufferStream.Write(stateObject.Buffer, 0, stateObject.Buffer.Length);

									callbackResult = new TransferEventArgs(Header.GenerateHeader(), this._bufferStream);

									received = true;
								}

								if (received)
								{
									Synchronization.InvokeAndCallback(() =>
									{
										return callbackResult;
									}, Received, null);

									Synchronization.InvokeAndCallback(() =>
									{
										return receivedLength;
									}, PartReceived, null);
								}
							}
							// 수신된 데이터 크기가 0이거나 그보다 작은 경우 소켓을 닫는다.
							else
							{
								this.DisconnectAsynchrous();
							}
						}
						catch (Exception error)
						{
							this.ThrowError(error);
						}
						finally
						{
							if (receivedSize > 0)
							{
								this.ReceiveAsynchrousFrom(ipEndPoint);
							}
						}
					}),
					packet);
			}
			catch (Exception error)
			{
				throw error;
			}
		}

		/// <summary>
		/// 데이터 수신에 대한 후처리를 진행한다.
		/// </summary>
		/// <param name="stateObject">수신된 데이터 패킷을 설정한다.</param>
		/// <param name="received"></param>
		/// <
		protected void AfterprocessForReceivingData(StateObject stateObject, ref bool received, out long receivedLength, ref TransferEventArgs result)
		{
			receivedLength = 0;

			try
			{
				// 수신해야 데이터 버퍼의 오프셋을 나타낸다.
				int currentOffset = 0;

				// 패킷 헤더가 존재하지 않는 경우
				if (this._currentHeader == null)
				{
					HeaderParseResult headerParseResult;

					// 헤더를 분석한다.
					var header = Header.ParseHeader(stateObject.Buffer, out headerParseResult, this.Endian == Endians.Little);

					// 헤더 분석에 성공한 경우 데이터 수신을 준비한다.
					if (headerParseResult == HeaderParseResult.Success)
					{
						this._currentHeader = header;

						this._bufferStream = new MemoryStream();

						// 헤더가 포함되어 있는 첫번째 블록 버퍼의 데이터 시작 위치를 설정한다.
						currentOffset = this._currentHeader.HeaderSize;

						switch (this._currentHeader.PacketType)
						{
							case PacketType.Raw:
							case PacketType.Serialized:
							case PacketType.Message:

								receivedLength = this._pieceBufferSize - currentOffset >= this._currentHeader.BodySize ?
									this._currentHeader.BodySize : this._pieceBufferSize - currentOffset;

								break;

							case PacketType.File:

								this._bufferStream.SetLength(this._currentHeader.FileSize);
								
								receivedLength = this._pieceBufferSize - currentOffset >= this._currentHeader.FileSize ?
									this._currentHeader.FileSize : this._pieceBufferSize - currentOffset;

								break;
						}
					}
				}
				else
				{
					switch (this._currentHeader.PacketType)
					{
						case PacketType.Raw:
						case PacketType.Serialized:
						case PacketType.Message:

							receivedLength = this._receivedOffset + this._pieceBufferSize < this._currentHeader.BodySize ?
								this._pieceBufferSize : this._currentHeader.BodySize - this._receivedOffset;

							break;

						case PacketType.File:

							receivedLength = this._receivedOffset + this._pieceBufferSize < this._currentHeader.FileSize ?
								this._pieceBufferSize : this._currentHeader.FileSize - this._receivedOffset;

							break;
					}
				}

				this._bufferStream.Write(stateObject.Buffer, currentOffset, (int)receivedLength);

				this._receivedOffset += receivedLength;

				// 전체 데이터 수신 여부를 판단한다.
				received = this._currentHeader.PacketType == PacketType.File ? this._receivedOffset >= this._currentHeader.FileSize : this._receivedOffset >= this._currentHeader.BodySize;

				// 데이터가 모두 수신된 상태인 경우, 수신 이벤트를 발생시킨다.
				if (received)
				{
					this._bufferStream.Seek(0, SeekOrigin.Begin);

					switch (this._currentHeader.PacketType)
					{
						// 데이터 유형이 Raw 데이터이거나 파일인 경우
						case PacketType.Raw:
						case PacketType.File:

							result = new TransferEventArgs(this._currentHeader, this._bufferStream);

							break;

						// 데이터 객체가 직렬화된 경우
						case PacketType.Serialized:

							BinaryFormatter formatter = new BinaryFormatter();

							object dataObject = formatter.Deserialize(this._bufferStream);

							result = new TransferEventArgs(this._currentHeader, dataObject);

							break;

						case PacketType.Message:

							byte[] receivedMessageBuffer = new byte[this._currentHeader.BodySize];

							this._bufferStream.Read(receivedMessageBuffer, 0, receivedMessageBuffer.Length);

							result = new TransferEventArgs(this._currentHeader, Encoding.UTF8.GetString(receivedMessageBuffer));

							break;
					}

					this._bufferStream = null;
					this._currentHeader = null;
					this._receivedOffset = 0;
				}
			}
			catch (Exception error)
			{
				this._bufferStream = null;
				this._currentHeader = null;
				this._receivedOffset = 0;

				this.ThrowError(error);
			}
		}

		/// <summary>
		/// 비동기 데이터 송신을 시작한다.
		/// </summary>
		/// <param name="data">송신하려는 데이터를 설정한다.</param>
		/// <param name="dataType">송신하려는 데이터의 객체 타입을 설정한다.</param>
		/// <param name="packetType">패킷 유형을 설정한다.</param>
		/// <param name="encryptionOption">암호화 옵션을 설정한다.</param>
		public void SendAsynchrous(byte[] data, string dataType = null, PacketType packetType = PacketType.Raw, EncryptionOptions encryptionOption = EncryptionOptions.None)
		{
			try
			{
				// 송신하려는 데이터의 객체 타입이 존재하지 않는 경우, 바이트 배열로 설정한다.
				if (dataType == null)
				{
					dataType = typeof(byte[]).FullName;
				}

				StateObject packet = new StateObject(this._socket);

				byte[] reservedData;

				if (this.UseInternalHeader)
				{
					packet.Header = this.PreprocessSendingData(data, dataType, packetType, encryptionOption, out reservedData);
					packet.Buffer = reservedData;
				}
				else
				{
					packet.Header = Header.GenerateHeader();
					packet.Buffer = data;
				}

				this._socket.BeginSend(packet.Buffer, 0, packet.Buffer.Length, SocketFlags.None,
					Synchronization.SynchronizeAsyncCallback((asyncResult) =>
					{
						Synchronization.InvokeAndCallback(() =>
						{
							this._socket.EndSend(asyncResult);

							StateObject resultPacket = asyncResult.AsyncState as StateObject;

							// 송신 콜백의 결과로는 헤더만 전달된다.
							return new TransferEventArgs(resultPacket.Header);
						}, Sent, null);
					}), packet);
			}
			catch (Exception error)
			{
				this.ThrowError(error);
			}
		}

		/// <summary>
		/// 비동기 객체 송신을 시작한다.
		/// </summary>
		/// <param name="instance">송신하려는 객체를 설정한다.</param>
		/// <param name="encryptionOption">암호화 옵션을 설정한다.</param>
		public void SendObject(object instance, EncryptionOptions encryptionOption = EncryptionOptions.None)
		{
			try
			{
				BinaryFormatter formatter = new BinaryFormatter();

				using (MemoryStream stream = new MemoryStream())
				{
					formatter.Serialize(stream, instance);

					byte[] binary = new byte[stream.Length];

					stream.Seek(0, SeekOrigin.Begin);

					stream.Read(binary, 0, binary.Length);

					this.SendAsynchrous(binary, instance.GetType().FullName, PacketType.Serialized, encryptionOption);
				}
			}
			catch (Exception error)
			{
				throw error;
			}
		}

		/// <summary>
		/// 비동기 메시지 송신을 시작한다.
		/// </summary>
		/// <param name="message">송신할 메시지를 설정한다.</param>
		/// <param name="encryptionOption">암호화 옵션을 설정한다.</param>
		public void SendMessage(string message, EncryptionOptions encryptionOption = EncryptionOptions.None)
		{
			try
			{
				byte[] data = Encoding.UTF8.GetBytes(message);

				this.SendAsynchrous(data, typeof(string).FullName, PacketType.Message, encryptionOption);
			}
			catch (Exception error)
			{
				this.ThrowError(error);
			}
		}

		/// <summary>
		/// 비동기 파일 송신을 시작한다.
		/// </summary>
		/// <param name="filePath">송신하려는 파일의 경로를 설정한다.</param>
		/// <remarks>
		/// <para>대용량 파일을 위해 파일은 암호화하지 않는다.</para>
		/// </remarks>
		public void SendFile(string filePath)
		{
			try
			{
				if (!File.Exists(filePath))
				{
					throw new FileNotFoundException("File to be sent doesn't exist.", filePath);
				}

				StateObject packet = new StateObject(this._socket);

				byte[] headerBinary;

				packet.Header = this.PreprocessSendingFile(filePath, out headerBinary);

				this._socket.BeginSendFile(filePath, headerBinary, null, TransmitFileOptions.UseDefaultWorkerThread,
					Synchronization.SynchronizeAsyncCallback((asyncResult) =>
					{
						Synchronization.InvokeAndCallback(() =>
						{
							this._socket.EndSend(asyncResult);

							StateObject resultPacket = asyncResult.AsyncState as StateObject;

							// 송신 콜백의 결과로는 헤더만 전달된다.
							return new TransferEventArgs(resultPacket.Header);
						}, Sent, null);
					}), packet);
			}
			catch (Exception error)
			{
				this.ThrowError(error);
			}
		}

		public void SendChunkedFile(string filePath)
		{
			try
			{
				if (!File.Exists(filePath))
				{
					throw new FileNotFoundException("File to be sent doesn't exist.", filePath);
				}

				StateObject packet = new StateObject(this._socket);
			}
			catch (Exception error)
			{
				this.ThrowError(error);
			}
		}

		/// <summary>
		/// 지정된 네트워크 장치로 비동기 데이터 송신을 시작한다.
		/// </summary>
		/// <param name="ipEndPoint">데이터를 수신하는 대상의 <see cref="System.Net.IPEndPoint"/>를 설정한다.</param>
		/// <param name="data">송신하려는 데이터를 설정한다.</param>
		/// <param name="dataType">송신하려는 데이터의 객체 타입을 설정한다.</param>
		/// <param name="packetType">패킷 유형을 설정한다.</param>
		/// <param name="encryptionOption">암호화 옵션을 설정한다.</param>
		public void SendAsynchrousTo(IPEndPoint ipEndPoint, byte[] data, string dataType = null, PacketType packetType = PacketType.Raw, EncryptionOptions encryptionOption = EncryptionOptions.None)
		{
			try
			{
				EndPoint endPoint = ipEndPoint as IPEndPoint;

				// 송신하려는 데이터의 객체 타입이 존재하지 않는 경우, 바이트 배열로 설정한다.
				if (dataType == null)
				{
					dataType = typeof(byte[]).FullName;
				}

				StateObject packet = new StateObject(this._socket);

				byte[] reservedData;

				packet.Header = this.PreprocessSendingData(data, dataType, packetType, encryptionOption, out reservedData);
				packet.Buffer = reservedData;

				this._socket.BeginSendTo(packet.Buffer, 0, packet.Buffer.Length, SocketFlags.None, endPoint,
					Synchronization.SynchronizeAsyncCallback((asyncResult) =>
					{
						Synchronization.InvokeAndCallback(() =>
						{
							this._socket.EndSendTo(asyncResult);

							StateObject resultPacket = asyncResult.AsyncState as StateObject;

							// 송신 콜백의 결과로는 헤더만 전달된다.
							return new TransferEventArgs(resultPacket.Header);
						}, Sent, null);
					}),
					packet);
			}
			catch (Exception error)
			{
				throw error;
			}
		}

		/// <summary>
		/// 지정된 네트워크 장치로 비동기 객체 송신을 시작한다.
		/// </summary>
		/// <param name="ipEndPoint">데이터를 수신하는 대상의 <see cref="System.Net.IPEndPoint"/>를 설정한다.</param>
		/// <param name="instance">송신하려는 객체를 설정한다.</param>
		/// <param name="encryptionOption">암호화 옵션을 설정한다.</param>
		public void SendObjectTo(IPEndPoint ipEndPoint, object instance, EncryptionOptions encryptionOption = EncryptionOptions.None)
		{
			try
			{
				BinaryFormatter formatter = new BinaryFormatter();

				using (MemoryStream stream = new MemoryStream())
				{
					formatter.Serialize(stream, instance);

					byte[] binary = new byte[stream.Length];

					stream.Seek(0, SeekOrigin.Begin);

					stream.Read(binary, 0, binary.Length);

					this.SendAsynchrousTo(ipEndPoint, binary, instance.GetType().FullName, PacketType.Serialized, encryptionOption);
				}
			}
			catch (Exception error)
			{
				throw error;
			}
		}

		/// <summary>
		/// 지정된 네트워크 장치로 비동기 메시지 송신을 시작한다.
		/// </summary>
		/// <param name="ipEndPoint">데이터를 수신하는 대상의 <see cref="System.Net.IPEndPoint"/>를 설정한다.</param>
		/// <param name="message">송신할 메시지를 설정한다.</param>
		/// <param name="encryptionOption">암호화 옵션을 설정한다.</param>
		public void SendMessageTo(IPEndPoint ipEndPoint, string message, EncryptionOptions encryptionOption = EncryptionOptions.None)
		{
			try
			{
				byte[] data = Encoding.UTF8.GetBytes(message);

				this.SendAsynchrousTo(ipEndPoint, data, typeof(string).FullName, PacketType.Message, encryptionOption);
			}
			catch (Exception error)
			{
				this.ThrowError(error);
			}
		}

		/// <summary>
		/// 데이터 송신에 대한 전처리를 진행한다.
		/// </summary>
		/// <param name="data">송신하려는 데이터를 설정한다.</param>
		/// <param name="dataType">보내려는 데이터의 타입명을 설정한다.</param>
		/// <param name="packetType">패킷 유형을 설정한다.</param>
		/// <param name="encryptionOptions">암호화 옵션을 설정한다.</param>
		/// <param name="reservedData">전처리된 송신 데이터를 참조한다.</param>
		/// <returns>송신할 데이터의 헤더 정보가 반환된다.</returns>
		protected Header PreprocessSendingData(byte[] data, string dataType, PacketType packetType, EncryptionOptions encryptionOptions, out byte[] reservedData)
		{
			Header header = null;

			reservedData = null;

			try
			{
				byte[] headerBinary;

				// 헤더를 생성한다.
				header = Header.GenerateHeader(data.Length, dataType, packetType, encryptionOptions, out headerBinary);

				// 송신할 데이터를 헤더의 크기와 원본 데이터의 크기를 합한 크기로 생성한다.
				reservedData = new byte[header.HeaderSize + data.Length];

				// 헤더와 원본 데이터를 송신할 데이터에 복사한다.
				Array.Copy(headerBinary, reservedData, headerBinary.Length);
				Array.Copy(data, 0, reservedData, headerBinary.Length, data.Length);
			}
			catch (Exception error)
			{
				this.ThrowError(error);
			}

			return header;
		}

		/// <summary>
		/// 파일 송신에 대한 전처리를 진행한다.
		/// </summary>
		/// <param name="filePath">송신하려는 파일의 전체 경로를 설정한다.</param>
		/// <param name="headerBinary">전처리된 헤더 정보를 참조한다.</param>
		/// <returns>전처리된 헤더가 반환된다.</returns>
		protected Header PreprocessSendingFile(string filePath, out byte[] headerBinary)
		{
			FileInfo file = new FileInfo(filePath);

			return Header.GenerateHeader(file.Length, file.Name, out headerBinary);
		}

		#endregion

		#region Keep Alive

		/// <summary>
		/// Keep Alive 옵션을 설정한다.
		/// </summary>
		/// <param name="turn">Keep Alive 옵션 활성화 여부를 설정한다.</param>
		/// <param name="aliveTime">Keep Alive 신호를 주고 받는 주기를 설정한다.</param>
		/// <param name="aliveInterval">Keep Alive 신호에 대한 응답이 없을 때, Keep Alive 신호를 반복적으로 주고 받는 주기를 설정한다.</param>
		protected void TurnKeepAlive(bool turn, uint aliveTime, uint aliveInterval)
		{
			try
			{
				TcpKeepAliveOptions keepAliveOptions = new TcpKeepAliveOptions();

				keepAliveOptions._turn = Convert.ToUInt32(turn);
				keepAliveOptions._keepAliveTime = aliveTime;
				keepAliveOptions._keepAliveInterval = aliveInterval;

				this._socket.IOControl(IOControlCode.KeepAliveValues, keepAliveOptions.ToArray(), null);
			}
			catch (Exception error)
			{
				throw error;
			}
		}

		#endregion
	}
}